/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package studio.raptor.sqlparser.dialect.mysql.parser;

import studio.raptor.sqlparser.ast.SQLDataType;
import studio.raptor.sqlparser.ast.SQLExpr;
import studio.raptor.sqlparser.ast.SQLName;
import studio.raptor.sqlparser.ast.SQLOrderBy;
import studio.raptor.sqlparser.ast.SQLOrderingSpecification;
import studio.raptor.sqlparser.ast.SQLPartition;
import studio.raptor.sqlparser.ast.SQLPartitionValue;
import studio.raptor.sqlparser.ast.SQLSubPartition;
import studio.raptor.sqlparser.ast.expr.SQLAggregateExpr;
import studio.raptor.sqlparser.ast.expr.SQLBinaryExpr;
import studio.raptor.sqlparser.ast.expr.SQLBinaryOpExpr;
import studio.raptor.sqlparser.ast.expr.SQLBinaryOperator;
import studio.raptor.sqlparser.ast.expr.SQLCharExpr;
import studio.raptor.sqlparser.ast.expr.SQLHexExpr;
import studio.raptor.sqlparser.ast.expr.SQLIdentifierExpr;
import studio.raptor.sqlparser.ast.expr.SQLIntegerExpr;
import studio.raptor.sqlparser.ast.expr.SQLMethodInvokeExpr;
import studio.raptor.sqlparser.ast.expr.SQLUnaryExpr;
import studio.raptor.sqlparser.ast.expr.SQLUnaryOperator;
import studio.raptor.sqlparser.ast.expr.SQLVariantRefExpr;
import studio.raptor.sqlparser.ast.statement.SQLAssignItem;
import studio.raptor.sqlparser.ast.statement.SQLColumnDefinition;
import studio.raptor.sqlparser.dialect.mysql.ast.MySqlPrimaryKey;
import studio.raptor.sqlparser.dialect.mysql.ast.MySqlUnique;
import studio.raptor.sqlparser.dialect.mysql.ast.MysqlForeignKey;
import studio.raptor.sqlparser.dialect.mysql.ast.MysqlForeignKey.Match;
import studio.raptor.sqlparser.dialect.mysql.ast.MysqlForeignKey.Option;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlCharExpr;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlExtractExpr;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlIntervalExpr;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlIntervalUnit;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlMatchAgainstExpr;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlMatchAgainstExpr.SearchModifier;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlOrderingExpr;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlOutFileExpr;
import studio.raptor.sqlparser.dialect.mysql.ast.expr.MySqlUserName;
import studio.raptor.sqlparser.parser.Lexer;
import studio.raptor.sqlparser.parser.ParserException;
import studio.raptor.sqlparser.parser.SQLExprParser;
import studio.raptor.sqlparser.parser.SQLSelectParser;
import studio.raptor.sqlparser.parser.Token;
import studio.raptor.sqlparser.util.JdbcConstants;

public class MySqlExprParser extends SQLExprParser {

  public static String[] AGGREGATE_FUNCTIONS = {"AVG", "COUNT", "GROUP_CONCAT", "MAX", "MIN",
      "STDDEV", "SUM"};

  public MySqlExprParser(Lexer lexer) {
    super(lexer, JdbcConstants.MYSQL);
    this.aggregateFunctions = AGGREGATE_FUNCTIONS;
  }

  public MySqlExprParser(String sql) {
    this(new MySqlLexer(sql));
    this.lexer.nextToken();
  }

  public SQLExpr relationalRest(SQLExpr expr) {
    if (identifierEquals("REGEXP")) {
      lexer.nextToken();
      SQLExpr rightExp = equality();

      rightExp = relationalRest(rightExp);

      return new SQLBinaryOpExpr(expr, SQLBinaryOperator.RegExp, rightExp, JdbcConstants.MYSQL);
    }

    return super.relationalRest(expr);
  }

  public SQLExpr multiplicativeRest(SQLExpr expr) {
    final Token token = lexer.token();
    if (token == Token.IDENTIFIER && "MOD".equalsIgnoreCase(lexer.stringVal())) {
      lexer.nextToken();
      SQLExpr rightExp = primary();

      rightExp = relationalRest(rightExp);

      return new SQLBinaryOpExpr(expr, SQLBinaryOperator.Modulus, rightExp, JdbcConstants.MYSQL);
    } else if (token == Token.DIV) {
      lexer.nextToken();
      SQLExpr rightExp = bitXor();
      expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.DIV, rightExp, getDbType());
      expr = multiplicativeRest(expr);
    }

    return super.multiplicativeRest(expr);
  }

  public SQLExpr notRationalRest(SQLExpr expr) {
    if (identifierEquals("REGEXP")) {
      lexer.nextToken();
      SQLExpr rightExp = primary();

      rightExp = relationalRest(rightExp);

      return new SQLBinaryOpExpr(expr, SQLBinaryOperator.NotRegExp, rightExp, JdbcConstants.MYSQL);
    }

    return super.notRationalRest(expr);
  }

  public SQLExpr primary() {
    final Token tok = lexer.token();

    if (identifierEquals("outfile")) {
      lexer.nextToken();
      SQLExpr file = primary();
      SQLExpr expr = new MySqlOutFileExpr(file);

      return primaryRest(expr);

    }

    switch (tok) {
      case LITERAL_ALIAS:
        String aliasValue = lexer.stringVal();
        lexer.nextToken();
        return primaryRest(new SQLCharExpr(aliasValue));
      case VARIANT:
        SQLVariantRefExpr varRefExpr = new SQLVariantRefExpr(lexer.stringVal());
        lexer.nextToken();
        if (varRefExpr.getName().equalsIgnoreCase("@@global")) {
          accept(Token.DOT);
          varRefExpr = new SQLVariantRefExpr(lexer.stringVal(), true);
          lexer.nextToken();
        } else if (varRefExpr.getName().equals("@") && lexer.token() == Token.LITERAL_CHARS) {
          varRefExpr.setName("@'" + lexer.stringVal() + "'");
          lexer.nextToken();
        } else if (varRefExpr.getName().equals("@@") && lexer.token() == Token.LITERAL_CHARS) {
          varRefExpr.setName("@@'" + lexer.stringVal() + "'");
          lexer.nextToken();
        }
        return primaryRest(varRefExpr);
      case VALUES:
        lexer.nextToken();
        if (lexer.token() != Token.LPAREN) {
          throw new ParserException("syntax error, illegal values clause");
        }
        return this.methodRest(new SQLIdentifierExpr("VALUES"), true);
      case BINARY:
        lexer.nextToken();
        if (lexer.token() == Token.COMMA || lexer.token() == Token.SEMI
            || lexer.token() == Token.EOF) {
          return new SQLIdentifierExpr("BINARY");
        } else {
          SQLUnaryExpr binaryExpr = new SQLUnaryExpr(SQLUnaryOperator.BINARY, expr());
          return primaryRest(binaryExpr);
        }
      case CACHE:
      case GROUP:
        lexer.nextToken();
        return primaryRest(new SQLIdentifierExpr(lexer.stringVal()));
      default:
        return super.primary();
    }

  }

  public final SQLExpr primaryRest(SQLExpr expr) {
    if (expr == null) {
      throw new IllegalArgumentException("expr");
    }

    if (lexer.token() == Token.LITERAL_CHARS) {
      if (expr instanceof SQLIdentifierExpr) {
        SQLIdentifierExpr identExpr = (SQLIdentifierExpr) expr;
        String ident = identExpr.getName();

        if (ident.equalsIgnoreCase("x")) {
          String charValue = lexer.stringVal();
          lexer.nextToken();
          expr = new SQLHexExpr(charValue);

          return primaryRest(expr);
        } else if (ident.equalsIgnoreCase("b")) {
          String charValue = lexer.stringVal();
          lexer.nextToken();
          expr = new SQLBinaryExpr(charValue);

          return primaryRest(expr);
        } else if (ident.startsWith("_")) {
          String charValue = lexer.stringVal();
          lexer.nextToken();

          MySqlCharExpr mysqlCharExpr = new MySqlCharExpr(charValue);
          mysqlCharExpr.setCharset(identExpr.getName());
          if (identifierEquals("COLLATE")) {
            lexer.nextToken();

            String collate = lexer.stringVal();
            mysqlCharExpr.setCollate(collate);
            accept(Token.IDENTIFIER);
          }

          expr = mysqlCharExpr;

          return primaryRest(expr);
        }
      } else if (expr instanceof SQLCharExpr) {
        SQLMethodInvokeExpr concat = new SQLMethodInvokeExpr("CONCAT");
        concat.addParameter(expr);
        do {
          String chars = lexer.stringVal();
          concat.addParameter(new SQLCharExpr(chars));
          lexer.nextToken();
        } while (lexer.token() == Token.LITERAL_CHARS || lexer.token() == Token.LITERAL_ALIAS);
        expr = concat;
      }
    } else if (lexer.token() == Token.IDENTIFIER) {
      if (expr instanceof SQLHexExpr) {
        if ("USING".equalsIgnoreCase(lexer.stringVal())) {
          lexer.nextToken();
          if (lexer.token() != Token.IDENTIFIER) {
            throw new ParserException("syntax error, illegal hex");
          }
          String charSet = lexer.stringVal();
          lexer.nextToken();
          expr.getAttributes().put("USING", charSet);

          return primaryRest(expr);
        }
      } else if ("COLLATE".equalsIgnoreCase(lexer.stringVal())) {
        lexer.nextToken();

        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }

        if (lexer.token() != Token.IDENTIFIER) {
          throw new ParserException("syntax error");
        }

        String collate = lexer.stringVal();
        lexer.nextToken();

        SQLBinaryOpExpr binaryExpr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.COLLATE,
            new SQLIdentifierExpr(collate), JdbcConstants.MYSQL);

        expr = binaryExpr;

        return primaryRest(expr);
      } else if (expr instanceof SQLVariantRefExpr) {
        if ("COLLATE".equalsIgnoreCase(lexer.stringVal())) {
          lexer.nextToken();

          if (lexer.token() != Token.IDENTIFIER) {
            throw new ParserException("syntax error");
          }

          String collate = lexer.stringVal();
          lexer.nextToken();

          expr.putAttribute("COLLATE", collate);

          return primaryRest(expr);
        }
      } else if (expr instanceof SQLIntegerExpr) {
        SQLIntegerExpr intExpr = (SQLIntegerExpr) expr;
        String binaryString = lexer.stringVal();
        if (intExpr.getNumber().intValue() == 0 && binaryString.startsWith("b")) {
          lexer.nextToken();
          expr = new SQLBinaryExpr(binaryString.substring(1));

          return primaryRest(expr);
        }
      }
    }

    if (lexer.token() == Token.LPAREN && expr instanceof SQLIdentifierExpr) {
      SQLIdentifierExpr identExpr = (SQLIdentifierExpr) expr;
      String ident = identExpr.getName();

      if ("EXTRACT".equalsIgnoreCase(ident)) {
        lexer.nextToken();

        if (lexer.token() != Token.IDENTIFIER) {
          throw new ParserException("syntax error");
        }

        String unitVal = lexer.stringVal();
        MySqlIntervalUnit unit = MySqlIntervalUnit.valueOf(unitVal.toUpperCase());
        lexer.nextToken();

        accept(Token.FROM);

        SQLExpr value = expr();

        MySqlExtractExpr extract = new MySqlExtractExpr();
        extract.setValue(value);
        extract.setUnit(unit);
        accept(Token.RPAREN);

        expr = extract;

        return primaryRest(expr);
      } else if ("SUBSTRING".equalsIgnoreCase(ident)) {
        lexer.nextToken();
        SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(ident);
        for (; ; ) {
          SQLExpr param = expr();
          methodInvokeExpr.addParameter(param);

          if (lexer.token() == Token.COMMA) {
            lexer.nextToken();
            continue;
          } else if (lexer.token() == Token.FROM) {
            lexer.nextToken();
            SQLExpr from = expr();
            methodInvokeExpr.addParameter(from);

            if (lexer.token() == Token.FOR) {
              lexer.nextToken();
              SQLExpr forExpr = expr();
              methodInvokeExpr.addParameter(forExpr);
            }
            break;
          } else if (lexer.token() == Token.RPAREN) {
            break;
          } else {
            throw new ParserException("syntax error");
          }
        }

        accept(Token.RPAREN);
        expr = methodInvokeExpr;

        return primaryRest(expr);
      } else if ("TRIM".equalsIgnoreCase(ident)) {
        lexer.nextToken();
        SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(ident);

        if (lexer.token() == Token.IDENTIFIER) {
          String flagVal = lexer.stringVal();
          if ("LEADING".equalsIgnoreCase(flagVal)) {
            lexer.nextToken();
            methodInvokeExpr.getAttributes().put("TRIM_TYPE", "LEADING");
          } else if ("BOTH".equalsIgnoreCase(flagVal)) {
            lexer.nextToken();
            methodInvokeExpr.getAttributes().put("TRIM_TYPE", "BOTH");
          } else if ("TRAILING".equalsIgnoreCase(flagVal)) {
            lexer.nextToken();
            methodInvokeExpr.putAttribute("TRIM_TYPE", "TRAILING");
          }
        }

        SQLExpr param = expr();
        methodInvokeExpr.addParameter(param);

        if (lexer.token() == Token.FROM) {
          lexer.nextToken();
          SQLExpr from = expr();
          methodInvokeExpr.putAttribute("FROM", from);
        }

        accept(Token.RPAREN);
        expr = methodInvokeExpr;

        return primaryRest(expr);
      } else if ("MATCH".equalsIgnoreCase(ident)) {
        lexer.nextToken();
        MySqlMatchAgainstExpr matchAgainstExpr = new MySqlMatchAgainstExpr();

        if (lexer.token() == Token.RPAREN) {
          lexer.nextToken();
        } else {
          exprList(matchAgainstExpr.getColumns(), matchAgainstExpr);
          accept(Token.RPAREN);
        }

        acceptIdentifier("AGAINST");

        accept(Token.LPAREN);
        SQLExpr against = primary();
        matchAgainstExpr.setAgainst(against);

        if (lexer.token() == Token.IN) {
          lexer.nextToken();
          if (identifierEquals("NATURAL")) {
            lexer.nextToken();
            acceptIdentifier("LANGUAGE");
            acceptIdentifier("MODE");
            if (lexer.token() == Token.WITH) {
              lexer.nextToken();
              acceptIdentifier("QUERY");
              acceptIdentifier("EXPANSION");
              matchAgainstExpr
                  .setSearchModifier(SearchModifier.IN_NATURAL_LANGUAGE_MODE_WITH_QUERY_EXPANSION);
            } else {
              matchAgainstExpr.setSearchModifier(SearchModifier.IN_NATURAL_LANGUAGE_MODE);
            }
          } else if (identifierEquals("BOOLEAN")) {
            lexer.nextToken();
            acceptIdentifier("MODE");
            matchAgainstExpr.setSearchModifier(SearchModifier.IN_BOOLEAN_MODE);
          } else {
            throw new ParserException("TODO");
          }
        } else if (lexer.token() == Token.WITH) {
          throw new ParserException("TODO");
        }

        accept(Token.RPAREN);

        expr = matchAgainstExpr;

        return primaryRest(expr);
      } else if (("CONVERT".equalsIgnoreCase(ident)) || ("CHAR".equalsIgnoreCase(ident))) {
        lexer.nextToken();
        SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr(ident);

        if (lexer.token() != Token.RPAREN) {
          exprList(methodInvokeExpr.getParameters(), methodInvokeExpr);
        }

        if (identifierEquals("USING")) {
          lexer.nextToken();
          if (lexer.token() != Token.IDENTIFIER) {
            throw new ParserException("syntax error");
          }
          String charset = lexer.stringVal();
          lexer.nextToken();
          methodInvokeExpr.putAttribute("USING", charset);
        }

        accept(Token.RPAREN);

        expr = methodInvokeExpr;

        return primaryRest(expr);
      } else if ("POSITION".equalsIgnoreCase(ident)) {
        accept(Token.LPAREN);
        SQLExpr subStr = this.primary();
        accept(Token.IN);
        SQLExpr str = this.expr();
        accept(Token.RPAREN);

        SQLMethodInvokeExpr locate = new SQLMethodInvokeExpr("LOCATE");
        locate.addParameter(subStr);
        locate.addParameter(str);

        expr = locate;
        return primaryRest(expr);
      }
    }

    if (lexer.token() == Token.VARIANT && "@".equals(lexer.stringVal())) {
      lexer.nextToken();
      MySqlUserName userName = new MySqlUserName();
      if (expr instanceof SQLCharExpr) {
        userName.setUserName(((SQLCharExpr) expr).toString());
      } else {
        userName.setUserName(((SQLIdentifierExpr) expr).getName());
      }

      if (lexer.token() == Token.LITERAL_CHARS) {
        userName.setHost("'" + lexer.stringVal() + "'");
      } else {
        userName.setHost(lexer.stringVal());
      }
      lexer.nextToken();
      return userName;
    }

    if (lexer.token() == Token.ERROR) {
      throw new ParserException(
          "syntax error, token: " + lexer.token() + " " + lexer.stringVal() + ", pos : "
              + lexer.pos());
    }

    return super.primaryRest(expr);
  }

  public SQLSelectParser createSelectParser() {
    return new MySqlSelectParser(this);
  }

  protected SQLExpr parseInterval() {
    accept(Token.INTERVAL);

    if (lexer.token() == Token.LPAREN) {
      lexer.nextToken();

      SQLMethodInvokeExpr methodInvokeExpr = new SQLMethodInvokeExpr("INTERVAL");
      if (lexer.token() != Token.RPAREN) {
        exprList(methodInvokeExpr.getParameters(), methodInvokeExpr);
      }

      accept(Token.RPAREN);

      //

      if (methodInvokeExpr.getParameters().size() == 1 //
          && lexer.token() == Token.IDENTIFIER) {
        SQLExpr value = methodInvokeExpr.getParameters().get(0);
        String unit = lexer.stringVal();
        lexer.nextToken();

        MySqlIntervalExpr intervalExpr = new MySqlIntervalExpr();
        intervalExpr.setValue(value);
        intervalExpr.setUnit(MySqlIntervalUnit.valueOf(unit.toUpperCase()));
        return intervalExpr;
      } else {
        return primaryRest(methodInvokeExpr);
      }
    } else {
      SQLExpr value = expr();

      if (lexer.token() != Token.IDENTIFIER) {
        throw new ParserException("Syntax error");
      }

      String unit = lexer.stringVal();
      lexer.nextToken();

      MySqlIntervalExpr intervalExpr = new MySqlIntervalExpr();
      intervalExpr.setValue(value);
      intervalExpr.setUnit(MySqlIntervalUnit.valueOf(unit.toUpperCase()));

      return intervalExpr;
    }
  }

  public SQLColumnDefinition parseColumn() {
    SQLColumnDefinition column = new SQLColumnDefinition();
    column.setName(name());
    column.setDataType(parseDataType());

    return parseColumnRest(column);
  }

  public SQLColumnDefinition parseColumnRest(SQLColumnDefinition column) {
    if (lexer.token() == Token.ON) {
      lexer.nextToken();
      accept(Token.UPDATE);
      SQLExpr expr = this.expr();
      column.setOnUpdate(expr);
    }
    if (identifierEquals("CHARSET")) {
      lexer.nextToken();
      MySqlCharExpr charSetCollateExpr = new MySqlCharExpr();
      charSetCollateExpr.setCharset(lexer.stringVal());
      lexer.nextToken();
      if (identifierEquals("COLLATE")) {
        lexer.nextToken();
        charSetCollateExpr.setCollate(lexer.stringVal());
        lexer.nextToken();
      }
      column.setCharsetExpr(charSetCollateExpr);
      return parseColumnRest(column);
    }
    if (identifierEquals("AUTO_INCREMENT")) {
      lexer.nextToken();
      column.setAutoIncrement(true);
      return parseColumnRest(column);
    }

    if (identifierEquals("precision") && column.getDataType().getName()
        .equalsIgnoreCase("double")) {
      lexer.nextToken();
    }

    if (lexer.token() == Token.PARTITION) {
      throw new ParserException("syntax error " + lexer.token() + " " + lexer.stringVal());
    }

    if (identifierEquals("STORAGE")) {
      lexer.nextToken();
      SQLExpr expr = expr();
      column.setStorage(expr);
    }

    if (lexer.token() == Token.AS) {
      lexer.nextToken();
      accept(Token.LPAREN);
      SQLExpr expr = expr();
      column.setAsExpr(expr);
      accept(Token.RPAREN);
    }

    if (identifierEquals("STORED")) {
      lexer.nextToken();
      column.setSorted(true);
    }

    if (identifierEquals("VIRTUAL")) {
      lexer.nextToken();
      column.setVirtual(true);
    }

    super.parseColumnRest(column);

    return column;
  }

  protected SQLDataType parseDataTypeRest(SQLDataType dataType) {
    super.parseDataTypeRest(dataType);

    if (identifierEquals("UNSIGNED")) {
      lexer.nextToken();
      dataType.getAttributes().put("UNSIGNED", true);
    }

    if (identifierEquals("ZEROFILL")) {
      lexer.nextToken();
      dataType.getAttributes().put("ZEROFILL", true);
    }

    return dataType;
  }

  public SQLExpr orRest(SQLExpr expr) {

    for (; ; ) {
      if (lexer.token() == Token.OR || lexer.token() == Token.BARBAR) {
        lexer.nextToken();
        SQLExpr rightExp = and();

        expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BooleanOr, rightExp,
            JdbcConstants.MYSQL);
      } else if (lexer.token() == Token.XOR) {
        lexer.nextToken();
        SQLExpr rightExp = and();

        expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.BooleanXor, rightExp,
            JdbcConstants.MYSQL);
      } else {
        break;
      }
    }

    return expr;
  }

  public SQLExpr additiveRest(SQLExpr expr) {
    if (lexer.token() == Token.PLUS) {
      lexer.nextToken();
      SQLExpr rightExp = multiplicative();

      expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Add, rightExp, JdbcConstants.MYSQL);
      expr = additiveRest(expr);
    } else if (lexer.token() == Token.SUB) {
      lexer.nextToken();
      SQLExpr rightExp = multiplicative();

      expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Subtract, rightExp, JdbcConstants.MYSQL);
      expr = additiveRest(expr);
    }

    return expr;
  }

  public SQLAssignItem parseAssignItem() {
    SQLAssignItem item = new SQLAssignItem();

    SQLExpr var = primary();

    String ident = null;
    if (var instanceof SQLIdentifierExpr) {
      ident = ((SQLIdentifierExpr) var).getName();

      if ("GLOBAL".equalsIgnoreCase(ident)) {
        ident = lexer.stringVal();
        lexer.nextToken();
        var = new SQLVariantRefExpr(ident, true);
      } else if ("SESSION".equalsIgnoreCase(ident)) {
        ident = lexer.stringVal();
        lexer.nextToken();
        var = new SQLVariantRefExpr(ident, false);
      } else {
        var = new SQLVariantRefExpr(ident);
      }
    }

    if ("NAMES".equalsIgnoreCase(ident)) {
      // skip
    } else if ("CHARACTER".equalsIgnoreCase(ident)) {
      var = new SQLIdentifierExpr("CHARACTER SET");
      accept(Token.SET);
      if (lexer.token() == Token.EQ) {
        lexer.nextToken();
      }
    } else {
      if (lexer.token() == Token.COLONEQ) {
        lexer.nextToken();
      } else {
        accept(Token.EQ);
      }
    }

    if (lexer.token() == Token.ON) {
      lexer.nextToken();
      item.setValue(new SQLIdentifierExpr("ON"));
    } else {
      item.setValue(this.expr());
    }

    item.setTarget(var);
    return item;
  }

  public SQLName nameRest(SQLName name) {
    if (lexer.token() == Token.VARIANT && "@".equals(lexer.stringVal())) {
      lexer.nextToken();
      MySqlUserName userName = new MySqlUserName();
      userName.setUserName(((SQLIdentifierExpr) name).getName());

      if (lexer.token() == Token.LITERAL_CHARS) {
        userName.setHost("'" + lexer.stringVal() + "'");
      } else {
        userName.setHost(lexer.stringVal());
      }
      lexer.nextToken();
      return userName;
    }
    return super.nameRest(name);
  }

  @Override
  public MySqlPrimaryKey parsePrimaryKey() {
    accept(Token.PRIMARY);
    accept(Token.KEY);

    MySqlPrimaryKey primaryKey = new MySqlPrimaryKey();

    if (identifierEquals("USING")) {
      lexer.nextToken();
      primaryKey.setIndexType(lexer.stringVal());
      lexer.nextToken();
    }

    accept(Token.LPAREN);
    for (; ; ) {
      primaryKey.addColumn(this.expr());
      if (!(lexer.token() == (Token.COMMA))) {
        break;
      } else {
        lexer.nextToken();
      }
    }
    accept(Token.RPAREN);

    return primaryKey;
  }

  public MySqlUnique parseUnique() {
    accept(Token.UNIQUE);

    if (lexer.token() == Token.KEY) {
      lexer.nextToken();
    }

    if (lexer.token() == Token.INDEX) {
      lexer.nextToken();
    }

    MySqlUnique unique = new MySqlUnique();

    if (lexer.token() != Token.LPAREN) {
      SQLName indexName = name();
      unique.setIndexName(indexName);
    }

    //5.5语法 USING BTREE 放在index 名字后
    if (identifierEquals("USING")) {
      lexer.nextToken();
      unique.setIndexType(lexer.stringVal());
      lexer.nextToken();
    }

    accept(Token.LPAREN);
    for (; ; ) {
      SQLExpr column = this.expr();
      if (lexer.token() == Token.ASC) {
        column = new MySqlOrderingExpr(column, SQLOrderingSpecification.ASC);
        lexer.nextToken();
      } else if (lexer.token() == Token.DESC) {
        column = new MySqlOrderingExpr(column, SQLOrderingSpecification.DESC);
        lexer.nextToken();
      }
      unique.addColumn(column);
      if (!(lexer.token() == (Token.COMMA))) {
        break;
      } else {
        lexer.nextToken();
      }
    }
    accept(Token.RPAREN);

    if (identifierEquals("USING")) {
      lexer.nextToken();
      unique.setIndexType(lexer.stringVal());
      lexer.nextToken();
    }

    return unique;
  }

  public MysqlForeignKey parseForeignKey() {
    accept(Token.FOREIGN);
    accept(Token.KEY);

    MysqlForeignKey fk = new MysqlForeignKey();

    if (lexer.token() != Token.LPAREN) {
      SQLName indexName = name();
      fk.setIndexName(indexName);
    }

    accept(Token.LPAREN);
    this.names(fk.getReferencingColumns());
    accept(Token.RPAREN);

    accept(Token.REFERENCES);

    fk.setReferencedTableName(this.name());

    accept(Token.LPAREN);
    this.names(fk.getReferencedColumns());
    accept(Token.RPAREN);

    if (identifierEquals("MATCH")) {
      if (identifierEquals("FULL")) {
        fk.setReferenceMatch(Match.FULL);
      } else if (identifierEquals("PARTIAL")) {
        fk.setReferenceMatch(Match.PARTIAL);
      } else if (identifierEquals("SIMPLE")) {
        fk.setReferenceMatch(Match.SIMPLE);
      }
    }

    while (lexer.token() == Token.ON) {
      lexer.nextToken();

      if (lexer.token() == Token.DELETE) {
        lexer.nextToken();

        Option option = parseReferenceOption();
        fk.setOnDelete(option);
      } else if (lexer.token() == Token.UPDATE) {
        lexer.nextToken();

        Option option = parseReferenceOption();
        fk.setOnUpdate(option);
      } else {
        throw new ParserException(
            "syntax error, expect DELETE or UPDATE, actual " + lexer.token() + " "
                + lexer.stringVal());
      }
    }
    return fk;
  }

  protected Option parseReferenceOption() {
    Option option;
    if (lexer.token() == Token.RESTRICT || identifierEquals("RESTRICT")) {
      option = Option.RESTRICT;
      lexer.nextToken();
    } else if (identifierEquals("CASCADE")) {
      option = Option.CASCADE;
      lexer.nextToken();
    } else if (lexer.token() == Token.SET) {
      lexer.nextToken();
      accept(Token.NULL);
      option = Option.SET_NULL;
    } else if (identifierEquals("ON")) {
      lexer.nextToken();
      if (identifierEquals("ACTION")) {
        option = Option.NO_ACTION;
        lexer.nextToken();
      } else {
        throw new ParserException("syntax error, expect ACTION, actual " + lexer.token() + " "
            + lexer.stringVal());
      }
    } else {
      throw new ParserException("syntax error, expect ACTION, actual " + lexer.token() + " "
          + lexer.stringVal());
    }

    return option;
  }

  protected SQLAggregateExpr parseAggregateExprRest(SQLAggregateExpr aggregateExpr) {
    if (lexer.token() == Token.ORDER) {
      SQLOrderBy orderBy = this.parseOrderBy();
      aggregateExpr.putAttribute("ORDER BY", orderBy);
    }
    if (identifierEquals("SEPARATOR")) {
      lexer.nextToken();

      SQLExpr seperator = this.primary();

      aggregateExpr.putAttribute("SEPARATOR", seperator);
    }
    return aggregateExpr;
  }

  public MySqlOrderingExpr parseSelectGroupByItem() {
    MySqlOrderingExpr item = new MySqlOrderingExpr();

    item.setExpr(expr());

    if (lexer.token() == Token.ASC) {
      lexer.nextToken();
      item.setType(SQLOrderingSpecification.ASC);
    } else if (lexer.token() == Token.DESC) {
      lexer.nextToken();
      item.setType(SQLOrderingSpecification.DESC);
    }

    return item;
  }

  public SQLPartition parsePartition() {
    accept(Token.PARTITION);

    SQLPartition partitionDef = new SQLPartition();

    partitionDef.setName(this.name());

    SQLPartitionValue values = this.parsePartitionValues();
    if (values != null) {
      partitionDef.setValues(values);
    }

    for (; ; ) {
      boolean storage = false;
      if (identifierEquals("DATA")) {
        lexer.nextToken();
        acceptIdentifier("DIRECTORY");
        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }
        partitionDef.setDataDirectory(this.expr());
      } else if (lexer.token() == Token.TABLESPACE) {
        lexer.nextToken();
        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }
        SQLName tableSpace = this.name();
        partitionDef.setTableSpace(tableSpace);
      } else if (lexer.token() == Token.INDEX) {
        lexer.nextToken();
        acceptIdentifier("DIRECTORY");
        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }
        partitionDef.setIndexDirectory(this.expr());
      } else if (identifierEquals("MAX_ROWS")) {
        lexer.nextToken();
        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }
        SQLExpr maxRows = this.primary();
        partitionDef.setMaxRows(maxRows);
      } else if (identifierEquals("MIN_ROWS")) {
        lexer.nextToken();
        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }
        SQLExpr minRows = this.primary();
        partitionDef.setMaxRows(minRows);
      } else if (identifierEquals("ENGINE") || //
          (storage = (lexer.token() == Token.STORAGE || identifierEquals("STORAGE")))) {
        if (storage) {
          lexer.nextToken();
        }
        acceptIdentifier("ENGINE");

        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }

        SQLName engine = this.name();
        partitionDef.setEngine(engine);
      } else if (lexer.token() == Token.COMMENT) {
        lexer.nextToken();
        if (lexer.token() == Token.EQ) {
          lexer.nextToken();
        }
        SQLExpr comment = this.primary();
        partitionDef.setComment(comment);
      } else {
        break;
      }
    }

    if (lexer.token() == Token.LPAREN) {
      lexer.nextToken();

      for (; ; ) {
        acceptIdentifier("SUBPARTITION");

        SQLName subPartitionName = this.name();
        SQLSubPartition subPartition = new SQLSubPartition();
        subPartition.setName(subPartitionName);

        partitionDef.addSubPartition(subPartition);

        if (lexer.token() == Token.COMMA) {
          lexer.nextToken();
          continue;
        }
        break;
      }

      accept(Token.RPAREN);
    }
    return partitionDef;
  }
}
